#ifndef __UtilityConfigFile_h__
#define __UtilityConfigFile_h__

#include <cstring>
#include <fstream>
#include <map>
#include <sstream>
#include <string>

namespace Utility
{
	class ConfigFile
	{
		private:
			static const int msMaxLineLength = 200;
		public:
			enum ExceptionList
			{
				ExceptionIncorrectConfigFile,
				ExceptionFileNotFound
			};
			class Array
			{
				private:
					mutable int			mIndex;
					char*				mArray;
					int					mLength;
					std::stringstream*	mConverter;

					inline Array(const Array &source);
				public:
					inline Array()
						: mIndex(0)
						, mArray(new char[1])
						, mLength(1)
						, mConverter(new std::stringstream())
					{
						mArray[0] = '\0';
					}
					inline ~Array()
					{
						delete [] mArray;
						delete mConverter;
					}
					inline void set(const char *value, const int length)
					{
						add(value, length);
					}
					inline void add(const char *value, const int length)
					{
						int newLength = mLength + length + 1;
						mArray = (char*)realloc(mArray, newLength);
						memcpy(mArray + mLength - 1, value, sizeof(char) * (length));
						mLength = newLength;
						mArray[newLength - 1] = '\0';
						mArray[newLength - 2] = '\0';
					}
					inline void begin() const
					{
						mIndex = 0;
					}
					inline bool end() const
					{
						return mArray[mIndex] == '\0';
					}
					template<typename ReturnType>
						ReturnType get() const
						{
							begin();
							return next<ReturnType>();
						}
					template<typename ReturnType>
						ReturnType next() const
						{
							ReturnType ret;
							
							mConverter->clear();
							*mConverter << next<const char*>();
							*mConverter >> ret;

							return ret;
						}
					template<>
						std::string next<std::string>() const
						{
							return next<const char*>();
						}
					template<>
						const char* next<const char*>() const
						{
							const char *ret = mArray + mIndex;
							mIndex += strlen(ret) + 1;
							return ret;
						}
			};

			class Config
				: public std::map<std::string, Array*>
			{
				private:
					inline Config(const Config &source);
				public:
					inline Config()
					{
					}
					inline ~Config()
					{
						for(Config::iterator i = begin(); i != end(); ++i)
							delete i->second;
					}
					inline void addConfig(const char *line, const int length)
					{
						int begin;

						int eqCharIndex = 0;
						while(eqCharIndex != length && line[eqCharIndex] != '=')
							++eqCharIndex;
						if(eqCharIndex == length)
							throw ConfigFile::ExceptionIncorrectConfigFile;
						if(eqCharIndex == 0)
							throw ConfigFile::ExceptionIncorrectConfigFile;
						bool isIncrementalEq = line[eqCharIndex - 1] == '+';
						int keyEnd = isIncrementalEq ? eqCharIndex - 1 : eqCharIndex;

						const char *key = line;
						ConfigFile::Trim(key, &begin, &keyEnd, keyEnd);
						if(keyEnd < begin)
							throw ConfigFile::ExceptionIncorrectConfigFile;
						++keyEnd;

						const char *value = line + eqCharIndex + 1;
						int valueEnd;
						ConfigFile::Trim(value, &begin, &valueEnd, length - eqCharIndex - 1);
						//if (valueEnd < begin)
						//	throw ConfigFile::ExceptionIncorrectConfigFile;
						value += begin;
						valueEnd -= begin - 1;

						Array *&a = (*this)[std::string(key, keyEnd)];
						if(!a)
							a = new Array();
						else if(!isIncrementalEq)
							throw ConfigFile::ExceptionIncorrectConfigFile;

						if(valueEnd > 0)
							a->add(value, valueEnd);
					}
					template<typename ReturnType>
						ReturnType get(const std::string &name) const
						{
							Config::const_iterator i = find(name);
							return i->second->get<ReturnType>();
						}
					const Array& getArray(const std::string &name) const
					{
						Config::const_iterator i = find(name);
						return *i->second;
					}
			};
		private:
			typedef std::map<std::string, Config*> ConfigMap;
			ConfigMap	mCategories;

			inline int getCommentStartIndex(const char *line) const
			{
				int index = 0;
				while(line[index] != '\0')
				{
					if(line[index] == ';')
						return index;
					++index;
				}
				return index;
			}
			inline bool isCategory(const char *line, const int length) const
			{
				return line[0] == '[' && line[length - 1] == ']';
			}

			inline ConfigFile(const ConfigFile &source);
		public:
			inline ConfigFile(const char *fileName)
			{
				std::ifstream input(fileName);
				if(!input.is_open())
					throw ExceptionFileNotFound;
				char line[msMaxLineLength];
				Config** current = 0;
				while(!input.eof())
				{
					input.getline(line, msMaxLineLength);
					int commentStartIndex = getCommentStartIndex(line);

					int begin;
					int end;
					Trim(line, &begin, &end, commentStartIndex);
					if(end <= begin)
						continue;

					const char* trimmedLine = line + begin;
					line[end + 1] = '\0';
					int lineLength = end - begin + 1;
					if(isCategory(trimmedLine, lineLength))
					{
						line[end] = '\0';
						++trimmedLine;
						current = &mCategories[trimmedLine];
						if(*current == 0)
							*current = new Config();
					}
					else
						(*current)->addConfig(trimmedLine, lineLength);
				}
			}
			inline ~ConfigFile()
			{
				for(ConfigMap::iterator i = mCategories.begin(); i != mCategories.end(); ++i)
					delete i->second;
			}
			inline const Config& get(const std::string& name) const
			{
				ConfigMap::const_iterator i = mCategories.find(name);
				return *i->second;
			}
			inline static bool IsWhiteSpace(const char c)
			{
				return c == ' ' || c == '\t';
			}
			inline static void Trim(const char *line, int *begin, int *end, const int commentStartIndex)
			{
				*begin = 0;
				while(*begin != commentStartIndex && IsWhiteSpace(line[*begin]))
					++(*begin);
				*end = commentStartIndex - 1;
				while(*end > *begin && IsWhiteSpace(line[*end]))
					--(*end);
			}
	};
}

#endif // __UtilityConfigFile_h__
